package com.cyy.ioc.tools;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.annotation.Resource;

import com.cyy.ioc.annotation.Autowired;
import com.cyy.ioc.annotation.Component;
import com.cyy.ioc.exceptions.InjectBeanException;

public class BeanUtils {
	
	private static final Map<String,BeanInfo> BEANS = new ConcurrentHashMap<String, BeanInfo>();
	
	// Logger logger = Logger.getLogger(BeanUtils.class.getName());
	
	public static boolean putBean(Object obj){
		return putBean(obj,Boolean.TRUE);
	}
	
	public static boolean putBean(Object obj,boolean single){
		if(obj == null){ return Boolean.FALSE; }
		String cls = obj.getClass().getName();
		BEANS.put(cls,new BeanInfo(cls,single,obj));
		return Boolean.TRUE;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getBean(Class<T> t){
		for(Map.Entry<String,BeanInfo> bean : BEANS.entrySet()){
			Object obj = bean.getValue().getData();
			if(obj == null){continue;}
			if(t.isAssignableFrom(obj.getClass()) || obj.getClass().isAssignableFrom(t)){
				return (T) obj;
			}
		}
		return null;
	}
	
	public static void injectBean(Object obj){
		Field [] fields = obj.getClass().getDeclaredFields();
		if(fields == null || fields.length == 0){
			return;
		}
		for(Field item : fields){
			Autowired autowired = item.getAnnotation(Autowired.class);
			Resource resource = item.getAnnotation(Resource.class);
			if(autowired == null && resource == null){
				System.out.println(item.getType() +" resource is null");
				continue;
			}
			System.out.println("object "+obj.getClass()+",inject");
			for(Map.Entry<String,BeanInfo> beans : BEANS.entrySet()){
				Object _data = beans.getValue().getData();
				if(_data == null || !item.getType().isAssignableFrom(_data.getClass())){
					continue;
				}
				try {
					item.setAccessible(true); // 取消 java 语音访问检查
					item.set(obj, beans.getValue().getData());
					return ;
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			throw new InjectBeanException("bean ["+obj.getClass().getName()+"] inject bean ["+item.getName()+"] is not find.");
		}
	}
	
	/**
	 * bean 扫描
	 * @param pack
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws URISyntaxException 
	 */
	public static final void beanScan(){
		beanScan("/");
	}
	public static final void beanScan(String basePackage){
		if(StringUtils.isNotEmpty(basePackage)){
			basePackage = basePackage.replaceAll("[.]","/");
		}
		URL contextUrl = BeanUtils.class.getResource("/");
		String contextPath =  StringUtils.encode(new File(contextUrl.getFile()).getAbsolutePath());
		Enumeration<URL> urls = null;
		try {
			urls = Thread.currentThread().getContextClassLoader().getResources(basePackage);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		if(urls == null){return ;}
		
		List<Class<?>> result = new LinkedList<>();
		while(urls.hasMoreElements()){
			urlScan(result,urls.nextElement(),contextPath);
		}
		for(Class<?> cls: result){
			try {
				Object obj = cls.newInstance();
				putBean(obj);
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		
		for(Map.Entry<String,BeanInfo> item : BEANS.entrySet()){
			Object obj = item.getValue().getOriginalData();
			if(obj != null){ // 执行注入操作
				injectBean(obj);
			}
		}
	}
	
	private static final void urlScan(List<Class<?>> listCls,URL url,String contextPath){
		String protocol = url.getProtocol();
		if("file".equals(protocol)){
			try {
				findClass(listCls,contextPath,new File(url.getFile()));
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if("jar".equals(protocol)){
			findJarClass(listCls,url);
		}
	}
	
	private static final void findClass(List<Class<?>> cls,String contextPath,File file) throws ClassNotFoundException, IOException{
		if(file.isFile() && file.getName().endsWith(".class")){
			String filePath = StringUtils.encode(file.getAbsolutePath());
			String className = StringUtils.decoder(filePath.replaceAll(contextPath,"").replaceAll("%5C","."));
			className = className.substring(0,className.length() - 6);
			if('.' == className.charAt(0)){
				className = className.substring(1);
			}
			addClass(cls,className);
		}
		if(file.isDirectory()){
			File [] fs = file.listFiles(new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					boolean acceptDir = pathname.isDirectory();
					boolean acceptFile = pathname.getName().endsWith(".class");
					return acceptDir || acceptFile;
				}
			});
			if(fs != null && fs.length > 0){
				for(File item : fs){
					findClass(cls,contextPath,item);
				}
			}
		}
	}
	/**
	 * 增加文件
	 * @param listCls
	 * @param classPath
	 */
	private static final void addClass(List<Class<?>> listCls,String classPath) throws ClassNotFoundException{
		Class<?> cls = Class.forName(classPath);
		if(cls.isAnnotationPresent(Component.class)){
			listCls.add(cls);
		}
	}
	
	/**
	 * 扫描 jar 中的 class 文件
	 */
	private static final void findJarClass(List<Class<?>> cls,URL jarUrl){
		try{
			JarURLConnection jarURLConnection = (JarURLConnection) jarUrl.openConnection();
			JarFile jarFile = jarURLConnection.getJarFile();
	        Enumeration<JarEntry> jarEntries = jarFile.entries();
	        while (jarEntries.hasMoreElements()) {
	            JarEntry jarEntry = jarEntries.nextElement();
	            String jarEntryName = jarEntry.getName();
	            if (jarEntryName.endsWith(".class")) {
	                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
	                addClass(cls,className);
	            }
	        }
        }catch(Exception e){
        	throw new InjectBeanException(e);
        }
	}
	
	public static Set<Object> getBeans(){
		Set<Object> objs = new HashSet<>(BEANS.size());
		for(Map.Entry<String,BeanInfo> item : BEANS.entrySet()){
			objs.add(item.getValue().getData());
		}
		return objs;
	}
	
}
